Skip to content

fix(verify,plugin-security,cli): bootStack honours the app-declared default permission set (#7001) - #7091

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-7001-bootstack-app-default-permission-set
Aug 9, 2026
Merged

fix(verify,plugin-security,cli): bootStack honours the app-declared default permission set (#7001)#7091
os-project-manager merged 1 commit into
mainfrom
claude/issue-7001-bootstack-app-default-permission-set

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes #7001

问题

两条启动路径对「应用声明的默认权限集是否存在」给出了不同答案:

  • objectstack serve 认它 —— 读取 config.permissions 中标记 isDefault: true 的权限集,作为 SecurityPlugin 的 fallbackPermissionSet 传入。
  • bootStack 不认 —— packages/verify/src/harness.ts 直接构造 vanilla new SecurityPlugin(),从不读 config.permissions

于是应用声明的 profile 在真人跑 CLI 时生效,在该应用自己的测试套件启动时静默缺席。这是 declared ≠ enforced,而且发生在专门用来捕捉这类偏差的测试载体内部:测试全绿,生产行为却不同。

两个锚点已在当前 origin/main 复核(卡片给的 serve.ts:2008 有漂移,实际调用在 2012;harness.ts419)。

#5491 之前这一点不可见:member_defaultobject_permissions['*'] 通配符,没有任何应用 profile 的成员照样能访问所有对象,fallback 从不承重。#5491 有意移除了这层地板,其 Migration 章节给出的唯一消费者动作 —— 通过 isDefault: true 提供应用默认 profile —— 恰恰是 bootStack 无法表达的。

做法

解析逻辑收敛到一处,两条路径都调用:appSecurityPluginOptions(config),新增于 @objectstack/plugin-security,与既有的 appDefaultPermissionSetName 并列。

它回答启动方真正的问题 —— 「这份 config 该给 SecurityPlugin 构造函数传什么」—— 而不只是名字。后半截 name ? { fallbackPermissionSet: name } : undefined 是一个决策而非格式选择:serve.ts 曾把它写死在原地,而 bootStack 压根没长出来过。返回 undefined 而非 { fallbackPermissionSet: undefined } 同样是刻意的 —— 前者让构造函数走自己的内置推导,后者只是碰巧命中同一分支,离「静默关掉平台基线」只差一次重构。

serve.ts 一并收敛到同一个 helper,两条路径从此按构造一致,而不是靠各自记得。

为什么不做更深的改法

考虑过让 SecurityPlugin 在 start() 自己去 metadata registry 里找 isDefault 权限集(那样连调用方都不必记得)。没有采纳,两个理由:fallbackPermissionSetinit() 就被注册成一个字符串服务,是快照,晚解析改不动它;而且按 helper 自己的文档,ADR-0090 P2 会用绑定到 everyone 的安装期建议整体替换这套 fallback 机制 —— 在一个即将被替换的机制上做架构改造是错的投资。收敛两条路径到一个 helper 是合适的尺寸

显式退出

opts.security 传入的实例整体胜出,永不被合并改写 —— 它到达时自带构造选项,悄悄改写其中一项会是比本次修复的 bug 更糟的意外。所以刻意需要平台原生基线的套件现在显式表达:

bootStack(config, { security: new SecurityPlugin() })

反向验证(两个方向都先定方向再跑)

  • 消融 A —— 还原 serve.ts 的原地写法:parity 契约测试 3 红,verify 自身 6 绿。这正是要点:除该契约外,仓库里没有任何东西盯着 serve 这一侧,当初两条路径就是这么分开的。
  • 消融 B —— 还原 harness.ts:parity 2 红 + verify 2 红(wiring 与行为各一)。

消融 B 还暴露出扫描本身的弱点:还原后未使用的 import 仍在,toContain('appSecurityPluginOptions') 保持绿 —— 提到不等于调用。断言因此改为测量构造式而非字符串。这条是实测出来的,不是推理出来的。

初始红也是诚实的红:wiring 为 member_default 而非声明值,行为侧返回 403(#5491 之后内置基线不再授予任何对象权限),即应用自己声明的默认 profile 授予的读被拒。

实测影响面

dogfood 86 个文件 / 524 个用例中,仅 1 条断言移动:

me-apps-and-everyone-baseline 断言 bootstrap 把 member_default 绑到 everyone 锚点,其文件头本就写着「Deliberately VANILLA」。该依赖真实存在但此前只由 harness 默认值静默表达,现在写进参数里 —— 正是本 issue 要的「显式退出而非静默默认」。断言本身一字未改。

#5491 时已手工搭建 test/showcase-security.ts(即这些 dogfood 应用本要证明不必要的「自定义 security 代码」)来补这个洞的 showcase 夹具,走 opts.security,完全不受影响。

证据:全套 dogfood 85 passed / 1 skipped / 521 tests passed。 所以是单方面翻转默认值,不需要逐套件判断。

验证

结果
@objectstack/verify test 5 files / 23 tests passed
@objectstack/plugin-security test 43 files / 878 tests passed
@objectstack/cli test 103 files / 1116 tests passed
dogfood 全套 85 passed, 1 skipped / 521 tests passed
4 个受影响包 typecheck 全 Done
pnpm lint 干净
lint.yml 全部 48 个 check: 闸门 全绿

check:type-check-debt 起初报 @objectstack/verify TEST_DEBT 8 → 9(+1)。查到是新测试文件的相对 import 少了 .js 后缀(NodeNext 下不解析,符号全变 any —— AGENTS.md 点名的那一类)。修因而非抬棘轮:补上后缀,计数回到 8,闸门转绿。

范围

🤖 Generated with Claude Code

https://claude.ai/code/session_01F8q5J1MQyocgtNspb15fSn


Generated by Claude Code

…efault permission set (#7001)

两条启动路径对「应用声明的默认权限集是否存在」给出了不同答案:
`objectstack serve` 会读取 `config.permissions` 中标记 `isDefault: true` 的权限集,
并作为 SecurityPlugin 的 `fallbackPermissionSet` 传入;而 `@objectstack/verify` 的
`bootStack` 直接构造了一个 vanilla `new SecurityPlugin()`,从不读取
`config.permissions`。于是应用声明的 profile 在真人执行 CLI 时生效,在该应用自己的
测试套件启动时却静默缺席 —— 这正是「declared ≠ enforced」,而且发生在专门用来捕捉
这类偏差的测试载体内部:测试全绿,生产行为却不同。

#5491 之前这一点不可见:平台的 `member_default` 带有 `object_permissions['*']`
通配符,没有任何应用 profile 的成员照样能访问所有对象,fallback 从来不承重。#5491
有意移除了这层地板,其 Migration 章节给出的唯一消费者动作 —— 通过 `isDefault: true`
提供应用默认 profile —— 恰恰是 `bootStack` 无法表达的。

解析逻辑现在只有一处,两条路径都调用它:`appSecurityPluginOptions(config)`,新增于
`@objectstack/plugin-security`,与既有的 `appDefaultPermissionSetName` 并列。它回答
启动方真正的问题 —— 「这份 config 该给 SecurityPlugin 构造函数传什么」—— 而不只是
名字,因为后半截 `name ? { fallbackPermissionSet: name } : undefined` 是一个决策而非
格式选择:serve.ts 曾把它写死在原地,而 bootStack 压根没长出来过。serve.ts 一并收敛
到同一个 helper,两条路径从此按构造一致,而不是靠各自记得。

行为变化仅限 `@objectstack/verify`:对声明了 `isDefault` 权限集的应用,
`bootStack(config)` 现在以该 profile 作为每请求可加性基线(ADR-0090 D5),与
`objectstack dev` 一致;未声明的应用完全不受影响(解析返回 `undefined`,插件继续从
内置集推导 `member_default`)。刻意需要平台原生基线的套件现在显式表达:
`bootStack(config, { security: new SecurityPlugin() })`;`opts.security` 传入的实例
整体胜出,永不被合并改写。

反向验证(两个方向都按预测):
- 还原 serve.ts 的原地写法 → parity 契约测试 3 红,verify 自身 6 绿(除该契约外,
  仓库里没有任何东西盯着 serve 这一侧)。
- 还原 harness.ts → parity 2 红 + verify 2 红。该消融还暴露出扫描本身的弱点:
  未使用的 import 让 `toContain('appSecurityPluginOptions')` 保持绿,故断言改为
  测量构造式而非字符串。

实测影响面:dogfood 86 个文件 / 524 个用例中,仅 1 条断言移动 ——
`me-apps-and-everyone-baseline`,其文件头本就写着「Deliberately VANILLA」。该依赖
真实存在但此前只由 harness 默认值静默表达,现在写进参数里。#5491 时已手工搭建
`test/showcase-security.ts` 来补这个洞的 showcase 夹具不受影响。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F8q5J1MQyocgtNspb15fSn
@vercel

vercel Bot commented Aug 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 9, 2026 2:39pm

Request Review

@github-actions github-actions Bot added the size/l label Aug 9, 2026
@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/cli, @objectstack/plugin-security, @objectstack/verify.

27 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via packages/cli)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli)
  • content/docs/automation/hook-bodies.mdx (via packages/cli)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli, @objectstack/plugin-security)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/validating-metadata.mdx (via packages/cli)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/cli)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/plugin-security)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/plugin-security)
  • content/docs/permissions/access-recipes.mdx (via packages/plugins/plugin-security)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli)
  • content/docs/permissions/authorization.mdx (via @objectstack/plugin-security)
  • content/docs/permissions/explain.mdx (via @objectstack/plugin-security)
  • content/docs/permissions/permissions-matrix.mdx (via packages/plugins/plugin-security)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/plugin-security)
  • content/docs/plugins/index.mdx (via @objectstack/cli, @objectstack/plugin-security)
  • content/docs/plugins/packages.mdx (via @objectstack/cli, @objectstack/plugin-security)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/ui/audience-based-interfaces.mdx (via packages/plugins/plugin-security)
  • content/docs/ui/dashboards.mdx (via @objectstack/plugin-security)

4 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/cli, @objectstack/plugin-security, @objectstack/verify)
  • content/docs/releases/v15.mdx (via @objectstack/verify)
  • content/docs/releases/v16.mdx (via @objectstack/cli)
  • content/docs/releases/v17.mdx (via @objectstack/cli)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 9, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 9, 2026 15:12
@os-project-manager
os-project-manager added this pull request to the merge queue Aug 9, 2026
Merged via the queue into main with commit d19fb5c Aug 9, 2026
39 of 41 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-7001-bootstack-app-default-permission-set branch August 9, 2026 15:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants